#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

#include "instructions.h"
#include "win32define.h"
#include "op_code.h"

/* Fichier de sortie standard (avant remplacement des labels). */
FILE *G_COutput = NULL ;

/* Dernière instruction rencontrée. */
enum OPCode G_LastInstruction = COP_NOP ;

/*********************/
/* GESTION DU BUFFER */
/*********************/

#define BUFFER_FILE "buffer.output"

/* Fichier du buffer. */
static FILE *G_Buffer = NULL ;

static int G_CurrentBuffer = 0 ;

/* 
   Démarre la mise en buffer des instructions, toutes les instructions
   ajoutés seront mises dans un buffer au lieu d'être ajoutées au fichier.
*/
int startBuffer (void) { 
    char *filename = malloc(strlen(BUFFER_FILE) + 2 + 1) ;
    sprintf(filename, "%s%d", BUFFER_FILE, G_CurrentBuffer ++) ;
    if (G_Buffer == NULL) {
	G_Buffer = fopen(filename, "w") ;
    }
    return G_Buffer == NULL ;
}

/* Stop la mise en buffer. */
void stopBuffer (void) {
    fclose(G_Buffer) ;
    G_Buffer = NULL ;
}

/* Inscrit le buffer dans le fichier de sortie. */
int flushBuffer (void) {
    char *filename = malloc(strlen(BUFFER_FILE) + 2 + 1) ;
    FILE *buffer  ;
    char c ;
    sprintf(filename, "%s%d", BUFFER_FILE, --G_CurrentBuffer) ;
    buffer = fopen(filename, "r") ;
    if (buffer == NULL) return 1 ;
    while ((c = fgetc(buffer)) != EOF) {
	fputc(c, G_COutput) ;
    }
    fclose(buffer) ;
    unlink(filename) ;
    return 0 ;
}


/****************************/
/* GESTION DES INSTRUCTIONS */
/****************************/

#define COMMENT_FORMAT "//%s\n"

void addComment (const char *comment) {
    fprintf(G_Buffer ? G_Buffer : G_COutput, COMMENT_FORMAT, comment) ;
    G_LastInstruction = COP_NOP ;
}

#define LABEL_FORMAT "%s:\n"

void addLabel (const char *label) {
    fprintf(G_Buffer ? G_Buffer : G_COutput, LABEL_FORMAT, label) ;
    G_LastInstruction = COP_NOP ;
}

#define JUMP_LABEL_FORMAT "L%d:\n"

void addJumpLabel (int num) {
    fprintf(G_Buffer ? G_Buffer : G_COutput, JUMP_LABEL_FORMAT, num) ;
    G_LastInstruction = COP_NOP ;
}

void addInstruction (const char *ins, ...) {
    va_list list ;
    FILE *output = G_Buffer ? G_Buffer : G_COutput ;
    fprintf(output, "\t") ;
    va_start(list, ins); 
    vfprintf(output, ins, list) ;
    fprintf(output, "\n") ;
    va_end(list) ;
}

void vaddInstruction (const char *ins, va_list list) {
    FILE *output = G_Buffer ? G_Buffer : G_COutput ;
    fprintf(output, "\t") ;
    vfprintf(output, ins, list) ;
    fprintf(output, "\n") ;
}


/* 
   Ajoute une instruction assembleur. 
   NOTE : Ne devrait être utilisé que pour ajouter les routines __asm__ !
*/
void INS_AddRoutine (const char *routine) {
    addInstruction(routine) ;
}

/**********/
/* LABELS */
/**********/

/* Ajoute un label (sous la forme label: ). */
void INS_Label (const char *label) { 
    addLabel(label) ;
    G_LastInstruction = COP_NOP ;
}

void INS_JumpLabel (int num) { 
    addJumpLabel(num) ;
    G_LastInstruction = COP_NOP ;
}

/*********/
/* JUMPS */
/*********/

static char const * const G_JumpTypes[] = {
    [JUMP_EQ] = "eq", 
    [JUMP_NE] = "ne", 
    [JUMP_LT] = "lt", 
    [JUMP_GE] = "ge", 
    [JUMP_LE] = "le", 
    [JUMP_GT] = "gt"
} ;

/* De même que pour les registres, on possède une fonction JUMP_TYPE qui, à partir
   d'un type entier de jump (ex: JUMP_EQ) renvoie l'attribut de jump correspondant
   (ex: "eq"). */
#define JUMP_TYPE(N) G_JumpTypes[N]

/* Ajoute un saut conditionnel, la condition dépend de type. */
void INS_Jump (enum JumpCondType type, int num) {
    addInstruction("j%s L%d", JUMP_TYPE(type), num) ;
}

/* Ajoute un saut inconditionnel. */
void INS_JumpUC (int num) {
    if (G_LastInstruction != COP_JMP) {
	addInstruction("jmp L%d", num) ;
	G_LastInstruction = COP_JMP ;
    }
}

static char const * const G_OperandeType[] = {
    ['D'] = "%s",
    ['I'] = "[%s]",
    ['V'] = "#%d",
    ['A'] = "[#%d]",
    ['L'] = "#%s"
} ;

/********/
/* CALL */
/********/

/* Fait un appel à la primitive call. */
void INS_Call (const char *function) {
    addInstruction("call %s", function) ;
}

void INS_Leave (void) {
    if (G_LastInstruction != COP_LEAVE) {
	addInstruction("leave") ;
	G_LastInstruction = COP_LEAVE ;
    }
}

/**************/
/* PUSH & POP */
/**************/

void INS_Push (const char *reg) {
    addInstruction("push %s", reg) ;
}

void INS_PushX (int firstReg, int lastReg) {
    if (firstReg == lastReg) {
	INS_Push(REGISTER(firstReg)) ;
    }
    else {
	addInstruction("push %s-%s", REGISTER(firstReg), REGISTER(lastReg)) ;
    }
}

void INS_Pop (const char *reg) {
    addInstruction("pop %s", reg) ;
}

void INS_PopX (int firstReg, int lastReg) {
    if (firstReg == lastReg) {
	INS_Pop(REGISTER(firstReg)) ;
    }
    else {
	addInstruction("pop %s-%s", REGISTER(firstReg), REGISTER(lastReg)) ;
    }
}

/**************/
/* OPERATIONS */
/**************/

static char const * const G_OperationTypes[] = {
    [OP_CMP] = "cmp",
    [OP_ADD] = "add", 
    [OP_SUB] = "sub", 
    [OP_MUL] = "mul", 
    [OP_DIV] = "div", 
    [OP_LSHIFT] = "lshift",
    [OP_RSHIFT] = "rshift",
    [OP_AND] = "and", 
    [OP_XOR] = "xor", 
    [OP_OR]  = "or",
    [OP_NOT] = "not"
} ;

#define INS_MAX_LENGTH 100

void INS_Operation (enum OperationType type, const char *operande, ...) {
    char instruction[INS_MAX_LENGTH] ;
    va_list list ;
    va_start(list, operande) ;
    /* On ajoute l'opération. */
    sprintf(instruction, "%s ", G_OperationTypes[type]) ;
    while (*operande) {
	strcat(instruction, G_OperandeType[(unsigned char)*operande]) ;
	if (*(++operande)) {
	    strcat(instruction, " ") ;
	}
    }
    vaddInstruction(instruction, list) ;
    va_end(list) ;
}


/********/
/* MOVs */
/********/

void INS_Mov (const char *ra, const char *rb) {
    if (ra != rb) {
	addInstruction("mov %s %s", ra, rb) ;
    }
}

void INS_Afc (const char *r, const int val) {
    addInstruction("afc %s #%d", r, val) ;
}


/****************/
/* LOAD / STORE */
/****************/

/* load RA, [RB] */
void INS_LoadFromReg  (const char *regDest, const char *reg) { 
    addInstruction("load %s (%s)", regDest, reg) ;
}

/* load RA, [RB + offset] */
void INS_LoadFromRegOffset (const char *regDest, const char *reg, const int offset) { 
    if (offset == 0) {
	INS_LoadFromReg(regDest, reg) ;
    }
    else {
	addInstruction("load %s %d(%s)", regDest, offset, reg) ; 
    }
}

/* load RA, [#addr] */
void INS_LoadFromAddr (const char *regDest, const int  addr) { 
    addInstruction("load %s (#%d)", regDest, addr) ; 
}

/* store [RA], RB */
void INS_StoreFromReg  (const char *regSrc, const char *reg) { 
    addInstruction("store (%s) %s", reg, regSrc) ; 
}

/* store [RA + offset], RB */
void INS_StoreFromRegOffset (const char *regSrc, const char *reg, const int offset) { 
    if (offset == 0) {
	INS_StoreFromReg (regSrc, reg) ;
    }
    else {
	addInstruction("store %d(%s) %s", offset, reg, regSrc) ;
    }
}

/* store [#addr], RB */
void INS_StoreFromAddr (const char *regSrc, const int  addr) {
    addInstruction("store (#%d) %s", addr, regSrc) ; 
}



/****************************/
/* GESTION DES COMMENTAIRES */
/****************************/

/* Ajoute un commentaire. */
void INS_Comment (const char *comment) {
    addComment(comment) ; 
} 

